/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.launch.internal;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.osgi.service.log.LogListener;

/**
 * LaunchActivator : The Launch bundle activator.
 * 
 * @author Romain DUBOIS
 */
public class LaunchActivator implements BundleActivator, FrameworkListener, BundleListener {

    /** The bundle context */
    private BundleContext mContext;
    /** The listener->display converter */
    private Converter mConverter;
    /** The launch logger */
    private LaunchLogger mLogger;
    /** The recorder */
    private Recorder mRecorder;
    /** The framework start flag */
    private boolean mStarted = false;

    /**
     * Constructor
     */
    public LaunchActivator() {
        super();
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
     */
    @Override
    public synchronized void bundleChanged(final BundleEvent pEvent) {
        if (pEvent.getType() == BundleEvent.STARTED && mRecorder != null) {
            mRecorder.bundleStarted(pEvent.getBundle());
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.framework.FrameworkListener#frameworkEvent(org.osgi.framework.FrameworkEvent)
     */
    @Override
    public synchronized void frameworkEvent(final FrameworkEvent pEvent) {
        // Not registered any more
        if (mRecorder == null) {
            return;
        }

        // Framework started : check if action is needed
        if (!mStarted && pEvent.getType() == FrameworkEvent.STARTED) {
            final FrameworkStartLevel lStartLevel = mRecorder.getStartLevel();
            if (lStartLevel != null) {
                final int lCurrentLevel = lStartLevel.getStartLevel();
                final int lTargetLevel = mRecorder.getStatus().getTargetLevel();

                // Action needed
                if (lCurrentLevel < lTargetLevel) {

                    // For first launch : wait for fileinstall instances
                    boolean lStart = true;
                    if (mRecorder.isFirstLaunch()) {
                        lStart = sleep(mRecorder.getInitWaitingTime(), 10);
                    }

                    // Change start level
                    if (lStart) {
                        mRecorder.update();
                        lStartLevel.setStartLevel(lCurrentLevel + 1);
                        mStarted = true;
                    }
                }
                // Target level already reached
                else {
                    stop(mContext);
                }
            }
        }

        // Notify recorder
        if (mStarted && pEvent.getType() == FrameworkEvent.STARTLEVEL_CHANGED && mRecorder != null) {
            final FrameworkStartLevel lStartLevel = mRecorder.getStartLevel();
            if (lStartLevel != null) {

                // Ignore redundant notifications
                if (lStartLevel.getStartLevel() <= mRecorder.getStatus().getCurrentLevel()) {
                    return;
                }

                boolean lFine = true;
                if (mRecorder.isFirstLaunch()) {
                    lFine = sleep(mRecorder.getStepWaitingTime(), 10);

                    // --------------------------------------------------------
                    // Temporary fix
                    // Felix + FileInstall do not resolve fragments installed
                    // when host is already resolved
                    // Waiting for a stable 3.2+ version of FileInstall

                    // Look for installed fragments
                    for (final Bundle lFragment : mContext.getBundles()) {
                        final String lHostName = lFragment.getHeaders()
                                .get(Constants.FRAGMENT_HOST);
                        if (lHostName != null && lFragment.getState() == Bundle.INSTALLED) {

                            // Look for host
                            for (final Bundle lHost : mContext.getBundles()) {
                                if (lHostName.equals(lHost.getSymbolicName())
                                        && lHost.getState() == Bundle.ACTIVE) {

                                    // Try to update the host bundle
                                    try {
                                        lHost.update();
                                    } catch (final BundleException e) {
                                        // Update failed, can not do better...
                                    }
                                }
                            }
                        }
                    }
                    // End of temporary fix
                    // --------------------------------------------------------
                }

                if (lFine) {
                    if (mRecorder.update()) {
                        stop(mContext);
                    } else {
                        lStartLevel.setStartLevel(lStartLevel.getStartLevel() + 1);
                    }
                }
            }
        }
    }

    /**
     * Sleep and update the converter.
     * 
     * @param pTotalTime
     *            The total sleep time
     * @param pSteps
     *            The number of sleep steps
     * @return If sleep processed wihtout problem
     */
    private boolean sleep(final long pTotalTime, final int pSteps) {
        try {
            for (int i = 0; i < pSteps; i++) {
                Thread.sleep(pTotalTime / pSteps);
                mConverter.updateInCurrentStep(i, pSteps, null);
            }
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            stop(mContext);
            return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    @Override
    public synchronized void start(final BundleContext pContext) {
        if (mRecorder == null) {
            mContext = pContext;

            mLogger = new LaunchLogger(mContext);
            mContext.registerService(LogListener.class.getName(), mLogger, null);

            mRecorder = new Recorder(mContext);
            mRecorder.init();

            mContext.addFrameworkListener(this);
            mContext.addBundleListener(this);

            mConverter = new Converter(mContext);

            // Init started bundles
            for (final Bundle lBundle : mContext.getBundles()) {
                if (lBundle.getState() == Bundle.ACTIVE) {
                    mRecorder.bundleStarted(lBundle);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public synchronized void stop(final BundleContext pContext) {
        if (mRecorder != null) {
            mConverter.ended();

            pContext.removeBundleListener(this);
            pContext.removeFrameworkListener(this);

            mRecorder.stop();
            mRecorder = null;

            mLogger.stop();
            mLogger = null;

            mContext = null;
        }
    }
}
